#!/usr/local/bin/python3
# *_* coding: UTF-8 *_*
# @IDE: PyCharm
# @Version: Python3.97
# @Author: Kendrick.Kc
# @Email: 509556985@qq.com
# @File: strategy.py
# @Inst: DGS策略
# @Time: 2022/8/27 14:32
# -----


import datetime
import time
import traceback

from binance_dgs.middle import Middle
from utils.timestamp import now_time_stamp
from utils.np import np_round


class DGS(Middle):
    def __init__(self, instId, GRID, m_lock, t_lock, binance):
        super(DGS, self).__init__(instId, GRID, m_lock, t_lock, binance)

    def dgs_run(self):

        # 变更当前区域
        self.cut_region()

        # 判断是否手动停止程序
        self._[self.binance.parameter.db_coll_program] = self.binance.cache_get(self.binance.parameter.db_coll_program)

        # 没有手动停止也还没到下一次统计K线的时间，继续开仓交易
        if not self._[self.binance.parameter.db_coll_program] and not self.res_db["info"]["next"]:
            # 这是【开仓】的遍历 -----------------------------------------------------------
            self.open_pos()

        # 这是【平仓】的遍历 -----------------------------------------------------------
        self.flat_pos()

        # 调整逐仓保证金
        self.margin_call()

        # 天单追踪止盈/止损方法 --------------------------------------------------------
        self.tian_pos()

        # 地单追踪止盈/止损方法 --------------------------------------------------------
        self.di_pos()

        # 到达天/地价格或持续交易到第N天后，所有仓位以市价平仓
        self.reach_price_stop_all_orders()

        # 只平不开状态时，当发现盈利仓位可覆盖亏损仓位后，立即平掉多空仓位
        self.profit_all_empty_orders()

        # 【实盘】如果交易所账户中为空仓时则校验DB数据表，与之对齐
        # self.check_pos_align()

        # 保存数据
        self.binance.cache_set(f"{self.binance.exchange_strategy}_{self.symbol}", self.res_db)

        # 当前是第二天或是手动停止吗？
        self.next_or_program_end()

    # 开仓方法【市价】 *************************************************************
    def open_pos(self, dgs_i=None):

        def closer(high, hh, price, low, ll):
            if abs(price - high) < abs(price - low):
                return high, hh
            else:
                return low, ll

        def _post_order(dgs, side, position_side, price):
            if dgs['state'] == "idle":
                ex_res = self.binance.rest("info",
                                           "new_order_test" if self.binance.pan_openings["pan"] else "new_order",
                                           symbol=self.symbol, side=side, positionSide=position_side, type="MARKET",
                                           quantity=dgs["amount"])
                if ex_res is not None:
                    dgs['state'] = "pending"
                    dgs['orderId'] = int(time.time()) if self.binance.pan_openings["pan"] else ex_res['orderId']
                    self.open_pos_real_u(dgs["amount"], dgs["price"])

                    self.binance.logger_info(f"{self.symbol} "
                                             f"【{'开多仓' if position_side == 'LONG' else '开空仓'}】, "
                                             f"网格号{dgs['number']}. 价格{price}, 数量{dgs['amount']}")
            return dgs

        if self.new_region != [] and self.new_region != self.last_region:
            ex_price = self.get_new_ticker_price(self.symbol)
            long = self.res_db["db"]["buy_kai_long"]
            if self.new_region[0] == 101 or self.new_region[0] == 99:
                dgs_ii = long[self.new_region[1]]
                res = closer(dgs_ii["beginPrice"], self.new_region[0], ex_price, dgs_ii["price"], self.new_region[1])
                if res[1] != self.new_region[0]:
                    dgs_i = res[1]
            else:

                res = closer(long[self.new_region[0]]["price"], self.new_region[0], ex_price,
                             long[self.new_region[1]]["price"], self.new_region[1])

                dgs_i = res[1]

            try:
                self.res_db["db"]["buy_kai_long"][dgs_i] = _post_order(self.res_db["db"]["buy_kai_long"][dgs_i],
                                                                       "BUY", "LONG", ex_price)
                self.res_db["db"]["sell_kai_short"][dgs_i] = _post_order(self.res_db["db"]["sell_kai_short"][dgs_i],
                                                                         "SELL", "SHORT", ex_price)
            except:
                self.binance.logger_error(f"下单失败({dgs_i}): {traceback.format_exc()}")
            self.last_region = self.new_region  # 更新区域

    # 平仓方法【市价】 *************************************************************
    def flat_pos(self):
        def _post_order(dgs, side, positionSide, price):
            for i in range(len(dgs)):
                if dgs[i]["state"] == "pending":
                    if positionSide == "LONG" and dgs[i]["coverPrice"] <= price \
                            or positionSide == "SHORT" and dgs[i]["coverPrice"] >= price:

                        res = self.binance.rest("info",
                                                "new_order_test" if self.binance.pan_openings["pan"] else "new_order",
                                                symbol=self.symbol, side=side, positionSide=positionSide, type="MARKET",
                                                quantity=dgs[i]["amount"])
                        if res is not None:
                            dgs[i]['state'], dgs[i]['orderId'] = "idle", ""
                            self.flat_pos_real_u(positionSide, dgs[i]["amount"], dgs[i]["price"], dgs[i]["coverPrice"])
                            self.binance.logger_info(f"{self.symbol} "
                                                     f"【{'平多仓' if positionSide == 'LONG' else '平空仓'}】, "
                                                     f"网格号{dgs[i]['number']}. 价格{price}, 数量{dgs[i]['amount']}")
                        break
            return dgs

        ex_price = self.get_new_ticker_price(self.symbol)
        self.res_db["db"]["buy_kai_long"] = _post_order(self.res_db["db"]["buy_kai_long"], "SELL", "LONG", ex_price)
        self.res_db["db"]["sell_kai_short"] = _post_order(self.res_db["db"]["sell_kai_short"], "BUY", "SHORT", ex_price)

    # 天单追踪止盈/止损方法【限价开, 市价平】 ***************
    def tian_pos(self):
        # 天单买入开多 -----------------------------------------------------------------------
        tian = self.res_db["db"]["speed_tracking"][0]
        price = self.get_new_ticker_price(self.symbol)
        if tian['pos'] == 's_t_buy_kai_long' and tian['state'] == "idle" and \
                not tian['open'] and price > tian['buyPrice']:

            # 没有手动停止也还没到下一次统计K线的时间，继续开仓交易
            if not self._[self.binance.parameter.db_coll_program] and not self.res_db["info"]["next"]:
                res = self.binance.rest("info", "new_order_test" if self.binance.pan_openings["pan"] else "new_order",
                                        symbol=self.symbol, side="BUY", positionSide="LONG", type="MARKET",
                                        quantity=str(tian["amount"]))
                if res is not None:
                    self.res_db["db"]["speed_tracking"][0]['state'] = 'pending'
                    self.res_db["db"]["speed_tracking"][0]['orderId'] = int(time.time()) if self.binance.pan_openings[
                        "pan"] else \
                        res['orderId']
                    self.res_db["db"]["speed_tracking"][0]['open'] = True
                    self.open_pos_real_u(tian["amount"], tian["buyPrice"])

                    self.binance.logger_info(f"{self.symbol} 【天单：开多仓】. 价格{price}, 数量{tian['amount']}")

        # 天单追踪止盈/止损 -----------------------------------------------------------------------
        if tian['pos'] == 's_t_buy_kai_long' and tian['state'] == "pending":
            if self.get_new_ticker_price(self.symbol) < tian['coverPrice']:
                res = self.binance.rest("info", "new_order_test" if self.binance.pan_openings["pan"] else "new_order",
                                        symbol=self.symbol, side="SELL", positionSide="LONG", type="MARKET",
                                        quantity=str(tian["amount"]))
                if res is not None:
                    self.res_db["db"]["speed_tracking"][0]['state'] = 'idle'
                    self.res_db["db"]["speed_tracking"][0]['orderId'] = ''
                    self.flat_pos_real_u("LONG", tian["amount"], tian["buyPrice"], tian["coverPrice"])
                    self.binance.logger_info(f"{self.symbol} 【天单：平多仓】. 价格{price}, 数量{tian['amount']}")
            else:
                cover_price = self.get_new_ticker_price(self.symbol) - tian['distance']
                if cover_price > tian['coverPrice']:
                    self.res_db["db"]["speed_tracking"][0]['coverPrice'] = cover_price

    # 地单追踪止盈/止损方法【限价开, 市价平】 ***************
    def di_pos(self):
        # 地单卖出开空 -----------------------------------------------------------------------
        di = self.res_db["db"]["speed_tracking"][1]
        price = self.get_new_ticker_price(self.symbol)
        if di['pos'] == 's_t_sell_kai_short' and di['state'] == "idle" and not di['open'] \
                and di['buyPrice'] > price:

            # 没有手动停止也还没到下一次统计K线的时间，继续开仓交易
            if not self._[self.binance.parameter.db_coll_program] and not self.res_db["info"]["next"]:
                res = self.binance.rest("info", "new_order_test" if self.binance.pan_openings["pan"] else "new_order",
                                        symbol=self.symbol, side="SELL", positionSide="SHORT", type="MARKET",
                                        quantity=di["amount"])
                if res is not None:
                    self.res_db["db"]["speed_tracking"][1]['state'] = 'pending'
                    self.res_db["db"]["speed_tracking"][1]['orderId'] = int(time.time()) if self.binance.pan_openings[
                        "pan"] else \
                        res['orderId']
                    self.res_db["db"]["speed_tracking"][1]['open'] = True
                    self.open_pos_real_u(di["amount"], di["buyPrice"])
                    self.binance.logger_info(f"{self.symbol} 【地单：开空仓】. 价格{price}, 数量{di['amount']}")

        # 地单追踪止盈/止损 -----------------------------------------------------------------------
        if di['pos'] == 's_t_sell_kai_short' and di['state'] == "pending":
            if self.get_new_ticker_price(self.symbol) > di['coverPrice']:
                res = self.binance.rest("info", "new_order_test" if self.binance.pan_openings["pan"] else "new_order",
                                        symbol=self.symbol, side="BUY", positionSide="SHORT", type="MARKET",
                                        quantity=di["amount"])
                if res is not None:
                    self.res_db["db"]["speed_tracking"][1]['state'] = 'idle'
                    self.res_db["db"]["speed_tracking"][1]['orderId'] = ''
                    self.flat_pos_real_u("SHORT", di["amount"], di["buyPrice"], di["coverPrice"])

                    self.binance.logger_info(f"{self.symbol} 【地单：平空仓】. 价格{price}, 数量{di['amount']}")
            else:
                cover_price = self.get_new_ticker_price(self.symbol) + di['distance']
                if cover_price < di['coverPrice']:
                    self.res_db["db"]["speed_tracking"][1]['coverPrice'] = cover_price

    # 到达天/地价格，所有dgs仓位以市价平仓
    def reach_price_stop_all_orders(self, days=3):

        price = self.get_new_ticker_price(self.symbol)

        # 天/地价格
        tian_price = self.res_db["db"]["speed_tracking"][0]["buyPrice"]
        di_price = self.res_db["db"]["speed_tracking"][1]["buyPrice"]

        # 相差天数
        start = datetime.datetime.fromtimestamp(self.res_db["info"]["start_number"] / 1000)
        end = datetime.datetime.fromtimestamp(now_time_stamp() / 1000)
        delta = end - start

        # 到达天地价格时所有DGS仓位以市价平仓（急速仓位除外）
        self.get_dgs_pos()
        if self._["DGS_BOO"] and price >= tian_price or price <= di_price:
            db_dgs_number = self.res_dgs_db_orders()
            number = db_dgs_number[0] + db_dgs_number[1]
            if number > 0:
                res_msg = f"【到达天价[{str(tian_price)}]或地价[{str(di_price)}], 当前现价:[{str(price)}], "
                res_msg2 = f"{self.symbol}交易对{res_msg}所有仓位以市价平仓: 清dgs仓】, 多空仓位总数量{str(number)}."
                self.stop_orders(db_dgs_number[0], db_dgs_number[1], res_msg2)

        elif delta.days >= days:
            db_dgs_number = self.res_dgs_db_orders()
            db_st_number = self.res_speed_tracking_db_orders()
            res_msg2 = f"{self.symbol}交易对【超过最大运行天数[{str(delta.days)}天], 所有仓位以市价平仓: 清dgs仓】, 多空仓位总数量"
            if self.binance.pan_openings["pan"]:
                number = db_dgs_number[0] + db_dgs_number[1]
                self.stop_orders(db_dgs_number[0], db_dgs_number[1], f"{res_msg2}{str(number)}.")
            else:
                number = db_st_number[0] + db_st_number[1]
                self.stop_orders(db_st_number[0], db_st_number[1], f"{res_msg2}{str(number)}.")

    # 只平不开状态时，当发现盈利仓位可覆盖亏损仓位后，立即平掉多空仓位
    def profit_all_empty_orders(self):

        # 没有手动停止也还没到下一次统计K线的时间，直接返回并继续交易
        if not self._[self.binance.parameter.db_coll_program] or not self.res_db["info"]["next"]:
            return

        # 在天地价格之外时
        price = self.get_new_ticker_price(self.symbol)
        # 天/地价格
        tian_price = self.res_db["db"]["speed_tracking"][0]["buyPrice"]
        di_price = self.res_db["db"]["speed_tracking"][1]["buyPrice"]
        if price >= tian_price or price <= di_price:
            return

        # 根据多空仓持仓量计算是否盈利以及是否全平仓
        db_dgs_number = self.res_dgs_db_orders()
        db_st_number = self.res_speed_tracking_db_orders()
        long_number = db_dgs_number[0] + db_st_number[0]
        short_number = db_dgs_number[1] + db_st_number[1]
        if self.binance.pan_openings["pan"]:    # 模拟盘
            # 未实现盈亏 = 持仓 *（最新价 - 买入均价)
            average_price = self.buy_average_price()
            long_price = long_number * (price - average_price[0])
            short_price = short_number * (price - average_price[1])

        else:   # 实盘
            data = self.binance.position_risk(self.symbol)
            long_price = float(data[0]["unRealizedProfit"])
            short_price = float(data[1]["unRealizedProfit"])

        if long_price > 0 or short_price > 0:
            if long_price > abs(short_price) or abs(long_price) < short_price:
                self.stop_orders(long_number, short_number, f"当前为只平不开状态, 发现盈利仓位可覆盖亏损仓位, 以市价平仓.")

    # 【实盘】如果交易所账户中为空仓时则校验DB数据表，与之对齐
    def check_pos_align(self):
        if self.binance.pan_openings["pan"]:
            return

        if not self._["LONG_BOO"]:
            for i in range(len(self.res_db["db"]["buy_kai_long"])):
                if self.res_db["db"]["buy_kai_long"][i]["state"] == "pending":
                    self.res_db["db"]["buy_kai_long"][i]["state"] = "idle"
                    self.res_db["db"]["buy_kai_long"][i]["orderId"] = ""
            self.res_db["db"]["speed_tracking"][0]["state"] = "idle"
            self.res_db["db"]["speed_tracking"][0]["orderId"] = ""

        if not self._["SHORT_BOO"]:
            for i in range(len(self.res_db["db"]["sell_kai_short"])):
                if self.res_db["db"]["sell_kai_short"][i]["state"] == "pending":
                    self.res_db["db"]["sell_kai_short"][i]["state"] = "idle"
                    self.res_db["db"]["sell_kai_short"][i]["orderId"] = ""
            self.res_db["db"]["speed_tracking"][1]["state"] = "idle"
            self.res_db["db"]["speed_tracking"][1]["orderId"] = ""

    # 调整逐仓保证金
    def margin_call(self):
        # 模拟交易暂无爆仓概念
        if self.binance.pan_openings["pan"]:
            return

        # 急速仓位不追加保证金(注意：如必须追加保证金，需判断平仓价是否到达强平价格
        if self.res_db["db"]["speed_tracking"][0]['state'] == "pending" or \
                self.res_db["db"]["speed_tracking"][1]['state'] == "pending":
            return

        self.get_pos()
        if not self._["BOO"]:
            return

        long_liquidation_price = float(self._['get_pos_long']['liquidationPrice'])  # 参考强平价格
        if self._["LONG_BOO"] and long_liquidation_price >= self.res_db["db"]["speed_tracking"][1]["buyPrice"]:
            self.binance.change_margin(self.symbol, "LONG", np_round(float(self._['get_pos_long']["isolatedMargin"]) * 1.01, 2))

        short_liquidation_price = float(self._['get_pos_short']["liquidationPrice"])  # 参考强平价格
        if self._["SHORT_BOO"] and short_liquidation_price <= self.res_db["db"]["speed_tracking"][0]["buyPrice"]:
            self.binance.change_margin(self.symbol, "SHORT", np_round(float(self._['get_pos_short']["isolatedMargin"]) * 1.01, 2))

    # 当前是第二天或是手动停止吗？
    def next_or_program_end(self):
        if now_time_stamp() > self.res_db["info"]["next_time_stamp"]:
            self.res_db["info"]["next"] = True

        if self._[self.binance.parameter.db_coll_program] or self.res_db["info"]["next"]:
            self.get_pos()
            if not self._["BOO"]:
                self.trading_end()
